﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.VCCodeModel;
using System.IO;

namespace GordonKristan.codeclean
{
	class CodeManagerModule
	{
		VCFileCodeModel file;
		TreeView codeTree;

		public CodeManagerModule(TreeView aView)
		{
			codeTree = aView;
			fetchCodeModel();
			setImages();
			refreshView();
		}

		/// <summary>
		/// Refreshes the treeView's contents based on the current file.
		/// </summary>
		private void refreshView()
		{
			//Delete all of the old nodes
			codeTree.Nodes.Clear();
			//Fetch the tree for the current file
			if (!fetchCodeModel())
			{
				return;
			}
			//Populate the tree with our data
			populateTree();
		}

		/// <summary>
		/// Fetches the CodeModel of the current file, if it exists.
		/// </summary>
		/// <returns>Whether or not it was successfully fetched</returns>
		private bool fetchCodeModel()
		{
			//Get the IDE object
			EnvDTE.DTE dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE));
			if (dte == null || dte.ActiveDocument == null)
			{
				return false;
			}
			//Get the code model for this file
			file = (VCFileCodeModel)dte.ActiveDocument.ProjectItem.FileCodeModel;
			if (file == null)
			{
				return false;
			}
			return true;
		}

		/// <summary>
		/// Populates the tree with all of the nodes.
		/// </summary>
		private void populateTree()
		{
			TreeNode node = new TreeNode(file.DTE.ActiveDocument.Name);
			node.ImageIndex = 20;
			node.SelectedImageIndex = node.ImageIndex;
			codeTree.Nodes.Add(node);
			//The elements field of the VCFileCodeModel only returns topmost
			//elements. So we don't have to worry about double additions.
			//Start with fields
			foreach (VCCodeElement el in file.CodeElements)
			{
				if (el.Kind == vsCMElement.vsCMElementVariable && isInFile(el))
				{
					addField(el, node);
				}
			}
			//Then functions
			foreach (VCCodeElement el in file.CodeElements)
			{
				if (el.Kind == vsCMElement.vsCMElementFunction && isInFile(el))
				{
					addMethod(el, node);
				}
			}
			//Then structs
			foreach (VCCodeElement el in file.CodeElements)
			{
				if (el.Kind == vsCMElement.vsCMElementStruct && isInFile(el))
				{
					addStruct(el, node);
				}
			}
			//Finally classes
			foreach (VCCodeElement el in file.CodeElements)
			{
				if (el.Kind == vsCMElement.vsCMElementClass && isInFile(el))
				{
					addClass(el, node);
				}
			}
		}

		/// <summary>
		/// Adds a class node as a child. This will then add the appropriate
		/// method and field nodes to the children it creates.
		/// </summary>
		/// <param name="aNode">The node to create a child for</param>
		private void addClass(VCCodeElement e, TreeNode aNode)
		{
			TreeNode node = new TreeNode(e.Name);
			node.ImageIndex = getImageIndex(e);
			node.SelectedImageIndex = node.ImageIndex;
			if (isInFile(e))
			{
				node.Tag = e.StartPoint.Line;
			}
			foreach (VCCodeElement el in e.Children)
			{
				if (el.Kind == vsCMElement.vsCMElementVariable && isInFile(el))
				{
					addField(el, node);
				}
			}
			foreach (VCCodeElement el in e.Children)
			{
				if (el.Kind == vsCMElement.vsCMElementFunction && isInFile(el))
				{
					addMethod(el, node);
				}
			}
			aNode.Nodes.Add(node);
		}

		/// <summary>
		/// Adds a struct node as a child.
		/// </summary>
		/// <param name="aNode">The node to create a child for</param>
		private void addStruct(VCCodeElement e, TreeNode aNode)
		{
			TreeNode node = new TreeNode(e.Name);
			node.ImageIndex = getImageIndex(e);
			node.SelectedImageIndex = node.ImageIndex;
			if (isInFile(e))
			{ 
				node.Tag = e.StartPoint.Line; 
			}
			foreach (VCCodeElement el in e.Children)
			{
				if (el.Kind == vsCMElement.vsCMElementVariable)
				{
					addField(el, node);
				}
			}
			aNode.Nodes.Add(node);
		}

		/// <summary>
		/// Adds a method node as a child.
		/// </summary>
		/// <param name="aNode">The node to create a child for</param>
		private void addMethod(VCCodeElement e, TreeNode aNode)
		{
			TreeNode node = new TreeNode(e.Name);
			node.ImageIndex = getImageIndex(e);
			node.SelectedImageIndex = node.ImageIndex;
			if (isInFile(e))
			{
				node.Tag = e.StartPoint.Line;
			}
			aNode.Nodes.Add(node);
		}

		/// <summary>
		/// Adds a field node as a child.
		/// </summary>
		/// <param name="aNode">The node to create a child for</param>
		private void addField(VCCodeElement e, TreeNode aNode)
		{
			TreeNode node = new TreeNode(e.Name);
			node.ImageIndex = getImageIndex(e);
			node.SelectedImageIndex = node.ImageIndex;
			if (isInFile(e))
			{
				node.Tag = e.StartPoint.Line;
			}
			aNode.Nodes.Add(node);
		}

		/// <summary>
		/// Determines whether a code element is explicitly declared in the
		/// current file, or if it's implementation is actually in another file.
		/// </summary>
		/// <param name="e">The code element to check</param>
		/// <returns>Whether it's in the file or not</returns>
		private bool isInFile(VCCodeElement e)
		{
			if (String.Compare(e.Location, file.DTE.ActiveDocument.FullName, true) == 0)
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// Loads the images and associates them with the treeView.
		/// </summary>
		private void setImages()
		{
			ImageList il = new ImageList();
			//Structures 0-4
			il.Images.Add(Properties.Resources.Structure);
			il.Images.Add(Properties.Resources.Structure_Friend);
			il.Images.Add(Properties.Resources.Structure_Private);
			il.Images.Add(Properties.Resources.Structure_Protected);
			il.Images.Add(Properties.Resources.Structure_Sealed);
			//Classes 5-9
			il.Images.Add(Properties.Resources.Class);
			il.Images.Add(Properties.Resources.Class_Friend);
			il.Images.Add(Properties.Resources.Class_Private);
			il.Images.Add(Properties.Resources.Class_Protected);
			il.Images.Add(Properties.Resources.Class_Sealed);
			//Methods 10-14
			il.Images.Add(Properties.Resources.Method);
			il.Images.Add(Properties.Resources.Method_Friend);
			il.Images.Add(Properties.Resources.Method_Private);
			il.Images.Add(Properties.Resources.Method_Protected);
			il.Images.Add(Properties.Resources.Method_Sealed);
			//Fields 15-19
			il.Images.Add(Properties.Resources.Field);
			il.Images.Add(Properties.Resources.Field_Friend);
			il.Images.Add(Properties.Resources.Field_Private);
			il.Images.Add(Properties.Resources.Field_Protected);
			il.Images.Add(Properties.Resources.Field_Sealed);
			//Misc 20-
			il.Images.Add(Properties.Resources.file);
			il.Images.Add(Properties.Resources.blank);

			codeTree.ImageList = il;
			codeTree.ImageIndex = 0;
			codeTree.SelectedImageIndex = 1;
		}

		/// <summary>
		/// Given a code element, decides which image it should have
		/// </summary>
		/// <param name="e">Element to check</param>
		/// <returns>Index of image icon</returns>
		private int getImageIndex(VCCodeElement e)
		{
			int ret = 0;
			switch (e.Kind)
			{
				case vsCMElement.vsCMElementFunction:
					{
						VCCodeFunction f = (VCCodeFunction)e;
						if (f.Access == vsCMAccess.vsCMAccessPrivate)
						{
							ret = 12;
						}
						else if (f.Access == vsCMAccess.vsCMAccessProtected)
						{
							ret = 13;
						}
						else if (f.IsSealed)
						{
							ret = 14;
						}
						else
						{
							ret = 10;
						}
						break;
					}
				case vsCMElement.vsCMElementClass:
					{
						VCCodeClass c = (VCCodeClass)e;
						if (c.Access == vsCMAccess.vsCMAccessPrivate)
						{
							ret = 7;
						}
						else if (c.Access == vsCMAccess.vsCMAccessProtected)
						{
							ret = 8;
						}
						else if (c.IsSealed)
						{
							ret = 9;
						}
						else
						{
							ret = 5;
						}
						break;
					}
				case vsCMElement.vsCMElementStruct:
					{
						VCCodeStruct s = (VCCodeStruct)e;
						if (s.Access == vsCMAccess.vsCMAccessPrivate)
						{
							ret = 2;
						}
						else if (s.Access == vsCMAccess.vsCMAccessProtected)
						{
							ret = 3;
						}
						else if (s.IsSealed)
						{
							ret = 4;
						}
						else
						{
							ret = 0;
						}
						break;
					}
				case vsCMElement.vsCMElementVariable:
					{
						VCCodeVariable v = (VCCodeVariable)e;
						if (v.Access == vsCMAccess.vsCMAccessPrivate)
						{
							ret = 17;
						}
						else if (v.Access == vsCMAccess.vsCMAccessProtected)
						{
							ret = 18;
						}
						else
						{
							ret = 15;
						}
						break;
					}
				default:
					return 21;
			}
			return ret;
		}
	}
}
